﻿using Infrastructure.AggregateBase;
using Infrastructure.DomainEvent;
using Infrastructure.UniOfWorkBase;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Domain
{
    /// <summary>
    /// 工作单实现类
    /// </summary>

    public class EFUnitOfWork : IEFUnitOfWork
    {
        public Guid ID { get; set; } = Guid.NewGuid();
        public IMonitor slowSqlWork { get; set; }
        public ILogger<EFUnitOfWork> Logger { get; set; }

        #region 属性
        public Infrastructure.DomainEvent.IEventDispatcher eventDispatcher { get; set; }
        //通过工作单元向外暴露的EF上下文对象

        public DbContext dbContext { get; set; }

        #endregion

        #region 构造函数
        public EFUnitOfWork()
        {
            //注册MEF
            //Regisgter.regisgter().ComposeParts(this);
        }
        #endregion

        #region IUnitOfWorkRepositoryContext接口
        public void RegisterNew<TEntity>(TEntity obj) where TEntity : AggregateRoot
        {
            var state = dbContext.Entry(obj).State;
            if (state == EntityState.Detached)
            {
                dbContext.Entry(obj).State = EntityState.Added;
            }
            IsCommitted = false;
        }

        public void RegisterModified<TEntity>(TEntity obj) where TEntity : AggregateRoot
        {
            if (dbContext.Entry(obj).State == EntityState.Detached)
            {
                dbContext.Set<TEntity>().Attach(obj);
            }
            dbContext.Entry(obj).State = EntityState.Modified;
            IsCommitted = false;
        }

        public void RegisterDeleted<TEntity>(TEntity obj) where TEntity : AggregateRoot
        {
            dbContext.Entry(obj).State = EntityState.Deleted;
            IsCommitted = false;
        }
        #endregion

        #region IUnitOfWork接口

        public bool IsCommitted { get; set; }

        public int Commit()
        {
            return Commit(new CancellationToken()).GetAwaiter().GetResult();
        }

        public async Task<int> Commit(CancellationToken cancellationToken = default(CancellationToken))
        {
            if (IsCommitted)
            {
                return 0;
            }


            dbContext.ChangeTracker.DetectChanges();

            var aggregateRoots = dbContext.ChangeTracker.Entries().ToList();
            try
            {
                int result = slowSqlWork.DoWork(dbContext, (dbContext) =>
                {
                    return dbContext.SaveChanges();
                });

                if (result > 0) await eventDispatcher.DispatchEntitysAsync(aggregateRoots, cancellationToken);

                IsCommitted = true;


                return result;
            }
            catch (DbUpdateException e)
            {
                Logger.LogError("SendEventError Domain:{0}", JsonConvert.SerializeObject(aggregateRoots.Select(i => i.Entity)));

                throw e;
            }
            catch (Exception e)
            {
                Logger.LogError("SendEventError Domain:{0}", JsonConvert.SerializeObject(aggregateRoots.Select(i => i.Entity)));

                throw e.InnerException;
            }
        }


        public void Rollback()
        {
            var aggregateRoots = dbContext.ChangeTracker.Entries().ToList();

            foreach (var item in aggregateRoots)
            {
                item.State = EntityState.Detached;
            }
            IsCommitted = false;
        }
        #endregion

        #region IDisposable接口
        public void Dispose()
        {
            //if (!IsCommitted)
            //{
            //    Commit();
            //}
            dbContext.Dispose();
        }

        public void Reset()
        {
            //var aggregateRoots = dbContext.ChangeTracker.Entries().ToList();

            //foreach (var item in aggregateRoots)
            //{
            //    item.State = EntityState.Detached;
            //}
            IsCommitted = false;
        }

        public IList<T> Query<T>(string sql, params object[] args) where T : class, new()
        {
            return dbContext.Database.SqlQuery<T>(sql, args);
        }

        #endregion
    }
}
